Skip to content

[๐ŸŒฑ JPA ๋ฐฉํƒˆ์ถœ ์˜ˆ์•ฝ ๋Œ€๊ธฐ] ๋‹ฌ์ˆ˜ ๋ฏธ์…˜ ์ œ์ถœํ•ฉ๋‹ˆ๋‹ค.#573

Open
soohyun1904 wants to merge 6 commits into
woowacourse:soohyun1904from
soohyun1904:step3
Open

[๐ŸŒฑ JPA ๋ฐฉํƒˆ์ถœ ์˜ˆ์•ฝ ๋Œ€๊ธฐ] ๋‹ฌ์ˆ˜ ๋ฏธ์…˜ ์ œ์ถœํ•ฉ๋‹ˆ๋‹ค.#573
soohyun1904 wants to merge 6 commits into
woowacourse:soohyun1904from
soohyun1904:step3

Conversation

@soohyun1904

@soohyun1904 soohyun1904 commented Jun 18, 2026

Copy link
Copy Markdown

[JPA ์ „ํ™˜] 1๋‹จ๊ณ„ โ€” ๋งคํ•‘ ๋ณ€ํ™˜ ยท ์—ฐ๊ด€๊ด€๊ณ„ ยท ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ ๊ด€์ฐฐ

์š”์•ฝ

JdbcTemplate ๊ธฐ๋ฐ˜ ์˜์†์„ฑ ๊ณ„์ธต์„ JPA๋กœ ์ „ํ™˜ํ•˜๋Š” ๋ฏธ์…˜์˜ 1๋‹จ๊ณ„๊นŒ์ง€ ์ง„ํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค. ๋„๋ฉ”์ธ ๋กœ์ง(Service/๋„๋ฉ”์ธ ๊ฐ์ฒด)์€ ๊ฑฐ์˜ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š๊ณ , ์˜์†์„ฑ ๊ณ„์ธต ๊ต์ฒด์™€ ์—”ํ‹ฐํ‹ฐ ๋งคํ•‘์— ์ง‘์ค‘ํ–ˆ์Šต๋‹ˆ๋‹ค.

  • ๋„๋‹ฌ: 1๋‹จ๊ณ„ (๋งคํ•‘ ๋ณ€ํ™˜ / ์—ฐ๊ด€๊ด€๊ณ„ ์–‘๋ฐฉํ–ฅ ํฌํ•จ / ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ ๊ด€์ฐฐ)
  • ๋‚จ์Œ: 2๋‹จ๊ณ„ ์ดํ›„ (N+1ยทfetch join ๋น„๊ต, ์—”ํ‹ฐํ‹ฐ ์ง์ ‘ ๋ฐ˜ํ™˜ ๊ตฌ๊ฐ„ DTO ์ „ํ™˜ ๋“ฑ)

1. ๋‹จ๊ณ„๋ณ„ ๋„๋‹ฌ ์ง€์ 

1-1. ๋งคํ•‘ ๋ณ€ํ™˜

  • build.gradle: spring-boot-starter-data-jpa ์ถ”๊ฐ€ (๊ธฐ์กด spring-boot-starter-jdbc ๋ณ‘์กด)
  • ์˜์กด์„ฑ ์—†๋Š” ์—”ํ‹ฐํ‹ฐ๋ถ€ํ„ฐ ๋งคํ•‘: Theme, ReservationTime
  • @Entity / @Id / @GeneratedValue(IDENTITY) ๋ถ€์—ฌ
  • VO๋Š” @Embeddable๋กœ: ThemeName, ThumbnailUrl, ReservationName, ReservationDate
  • enum์€ @Enumerated(EnumType.STRING)์œผ๋กœ (Status: APPROVED / WAITING)
  • created_at์€ DB ์ž๋™ ์ฑ„์›€: @Column(insertable=false, columnDefinition=...)
  • JpaRepository<T, Long> ์ธํ„ฐํŽ˜์ด์Šค๋กœ ๊ต์ฒด
  • findFamous ์ง‘๊ณ„ ์ฟผ๋ฆฌ๋„ @Query JPQL๋กœ ์ด๊ด€ ์™„๋ฃŒ

1-2. ์—ฐ๊ด€๊ด€๊ณ„ ๋งคํ•‘ (๋‹จ๋ฐฉํ–ฅ + ๋ถ€๋ถ„ ์–‘๋ฐฉํ–ฅ)

  • Reservation โ†’ Slot : @ManyToOne(optional=false) + @JoinColumn("slot_id") (์˜ค๋„ˆ ์ชฝ)
  • Slot โ†’ ReservationTime : @ManyToOne(optional=false) + @JoinColumn("time_id")
  • Slot โ†’ Theme : @ManyToOne(optional=false) + @JoinColumn("theme_id")
  • Slot โ†’ Reservation : @OneToMany(mappedBy="slot") โ€” ๊ฑฐ์šธ ์ชฝ, ์–‘๋ฐฉํ–ฅ ์œ ์ง€
  • cascade / orphanRemoval ๋ฏธ์ ์šฉ (์˜ˆ์•ฝ์€ ๋…๋ฆฝ ๊ธฐ๋ก, ์‚ญ์ œ๋Š” existsBy๋กœ ์ฐจ๋‹จ)
  • Rank๋Š” @Transient๋กœ ๋น„์˜์† ์ฒ˜๋ฆฌ (๋Ÿฐํƒ€์ž„ ๊ณ„์‚ฐ ๊ฐ’)

1-3. ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ ๊ด€์ฐฐ

๊ด€์ฐฐ ์ „์šฉ ํ…Œ์ŠคํŠธ 3์ข…์œผ๋กœ ํ˜„์ƒ ํ™•์ธ:

  • PersistenceContextObservationTest: dirty checking / 1์ฐจ ์บ์‹œ / ์“ฐ๊ธฐ ์ง€์—ฐ / JPQL ์‹คํ–‰ ์ „ auto-flush
  • FetchDefaultObservationTest: @ManyToOne ๊ธฐ๋ณธ๊ฐ’ EAGER vs @OneToMany ๊ธฐ๋ณธ๊ฐ’ LAZY, em.find() vs JPQL์˜ SQL ์ฐจ์ด
  • SlotReservationSyncTest: ๊ฑฐ์šธ ์ชฝ(slot.getReservations().add())๋งŒ ์ˆ˜์ • ์‹œ FK ๋ฏธ๋ฐ˜์˜, ์˜ค๋„ˆ ์ชฝ(reservation.slot) ์ˆ˜์ • ์‹œ ์ •์ƒ ๋ฐ˜์˜

๋‚จ์€ ๋‹จ๊ณ„ โฌœ

  • N+1 ์žฌํ˜„ ํ›„ fetch join์œผ๋กœ ํ•ด๊ฒฐ (2๋‹จ๊ณ„ ๊ณผ์ œ)
  • update ๋กœ์ง์˜ ๋ณ€๊ฒฝ ๊ฐ์ง€(dirty checking) ๋ฐฉ์‹ ์™„์ „ ์ „ํ™˜
  • ์—”ํ‹ฐํ‹ฐ ์ง์ ‘ ๋ฐ˜ํ™˜ ๊ตฌ๊ฐ„ DTO ๋ณ€ํ™˜

2. ๋ฐœํ–‰ SQL ๋ฐœ์ทŒ

INSERT โ€” JPA vs ๊ธฐ์กด JdbcTemplate

-- JPA
insert into reservation (name, slot_id, status, id)
values (?, ?, ?, default)

-- JdbcTemplate (๊ธฐ์กด)
INSERT INTO reservation (slot_id, name, status) VALUES (?, ?, ?)

ํ•ญ๋ชฉ JPA JdbcTemplate
id ์ฒ˜๋ฆฌ ์ปฌ๋Ÿผ ํฌํ•จ + default ํ‚ค์›Œ๋“œ ์ปฌ๋Ÿผ ์ž์ฒด๋ฅผ ์•ˆ ์”€
์ž‘์„ฑ ์ฃผ์ฒด @entity ๋ถ„์„ ํ›„ ์ž๋™ ์ƒ์„ฑ ๊ฐœ๋ฐœ์ž๊ฐ€ ์“ด ๋ฌธ์ž์—ด
๋ฐ”์ธ๋”ฉ/์‹คํ–‰ ์‹œ์  ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์— ์˜ฌ๋ ค๋‘๊ณ  flush ์‹œ์  ์‹คํ–‰ ?์— ๊ฐ’ ์„ธํŒ… ํ›„ ๋ฐ”๋กœ ์ „์†ก

dirty checking / 1์ฐจ ์บ์‹œ / LazyInit๋„ ๋™์ผ ํ˜•์‹์œผ๋กœ ๊ด€์ฐฐํ•จ.


4. ๋ง์„ค์ธ ๊ฒฐ์ •

๊ฒฐ์ • 1 โ€” ์–‘๋ฐฉํ–ฅ ์œ ์ง€ ์—ฌ๋ถ€

  • Slot์— @OneToMany(mappedBy="slot")๋ฅผ ์ ์šฉํ•ด ์–‘๋ฐฉํ–ฅ ๊ตฌ์„ฑ.
  • ๊ฒช์€ ๋ฌธ์ œ: (1) ๊ฑฐ์šธ ์ชฝ(slot.getReservations().add())๋งŒ ์ˆ˜์ •ํ•˜๋ฉด FK๊ฐ€ DB์— ๋ฐ˜์˜๋˜์ง€ ์•Š์Œ, (2) ์—”ํ‹ฐํ‹ฐ ์ง๋ ฌํ™” ์‹œ ์ˆœํ™˜์ฐธ์กฐ ์œ„ํ—˜.
  • ๊ทธ๋Ÿผ์—๋„ ์–‘๋ฐฉํ–ฅ ์œ ์ง€: SlotReservationSyncTest๋กœ ์˜ค๋„ˆ/๊ฑฐ์šธ ๋™๊ธฐํ™” ๊ทœ์น™์„ ๋ช…์‹œ์ ์œผ๋กœ ๊ฒ€์ฆํ•˜๊ณ , slot.getReservations() ํ™œ์šฉ ๊ฐ€๋Šฅ์„ฑ์„ ๋‚จ๊ฒจ๋‘ .
  • ๋‹จ, ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜์ง€ ์•Š๋„๋ก DTO ๋ณ€ํ™˜์„ ๋‚จ์€ ๊ณผ์ œ๋กœ ์ง€์ •. (cascade๋„ ์˜ˆ์•ฝ์€ ๋…๋ฆฝ ๊ธฐ๋ก์ด๋ฏ€๋กœ ๋ฏธ์ ์šฉ.)

5. ํ”๋“ค๋ฆฐ ํ•œ ์žฅ๋ฉด

OSIV(open-in-view) ์„ค์ • ๋•Œ๋ฌธ์— ํ•œ์ฐธ ํ—ค๋งธ์Œ.

  • ์–‘๋ฐฉํ–ฅ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ–ˆ๋Š”๋ฐ "๊ทธ๋ƒฅ ๋์Œ".
  • ์•Œ๊ณ  ๋ณด๋‹ˆ open-in-view๊ฐ€ ๊ธฐ๋ณธ true๋ผ ์„ธ์…˜์ด JSON ๋ณ€ํ™˜๊นŒ์ง€ ์—ด๋ ค ์žˆ์–ด์„œ์˜€์Œ.
  • spring.jpa.open-in-view=false๋กœ ์„ค์ •ํ•ด ๋น„ํ™œ์„ฑํ™” ์™„๋ฃŒ. (ํ‚ค ์ ‘๋‘์‚ฌ๋ฅผ ๋น ๋œจ๋ฆฌ๋Š” ์‹ค์ˆ˜๋„ ๊ฒฝํ—˜ํ•จ โ€” ์„ค์ •์ด ์กฐ์šฉํžˆ ๋ฌด์‹œ๋˜๋Š” ๊ฒƒ ํ™•์ธ.)
  • ๊ตํ›ˆ: "๊ทธ๋ƒฅ ๋˜๋Š” ๊ฒƒ"์ด ํ•ญ์ƒ ์ •์ƒ ๋™์ž‘์€ ์•„๋‹ˆ๋‹ค. OSIV๊ฐ€ LAZY๋Š” ๋„์™€์ค˜๋„ ์ˆœํ™˜์ฐธ์กฐ๋Š” ๋ชป ๋ง‰๋Š”๋‹ค๋Š” ๊ฑธ ์ง์ ‘ ํ„ฐ๋œจ๋ ค๋ณด๊ณ  ์•Œ๊ฒŒ ๋จ. โ†’ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ง์ ‘ ๋ฐ˜ํ™˜ํ•˜์ง€ ๋ง๊ณ  DTO๋กœ ๋ณ€ํ™˜.

ํ™•์ธ ๊ณผ์ œ ๋‹ต๋ณ€

Q1. ์˜ˆ์•ฝ ์ƒ์„ฑ INSERT SQL์ด ๊ธฐ์กด(JdbcTemplate)๊ณผ ์–ด๋–ป๊ฒŒ ๊ฐ™๊ณ  ๋‹ค๋ฅธ๊ฐ€? โ†’ "2. ๋ฐœํ–‰ SQL ๋ฐœ์ทŒ" ์ฐธ๊ณ . ๊ฐ™์€ ์ : ๋‘˜ ๋‹ค reservation์— INSERT. ๋‹ค๋ฅธ ์ : id ์ฒ˜๋ฆฌ ๋ฐฉ์‹, SQL ์ž‘์„ฑ ์ฃผ์ฒด, ๋ฐ”์ธ๋”ฉ/์‹คํ–‰ ์‹œ์ .

Q2. findById(reservationId).getSlot().getTime().getStartAt()์ด ๋ฐœํ–‰ํ•˜๋Š” SQL? โ†’ Reservation ๋กœ๋“œ ์‹œ slot_id FK๋งŒ ์•Œ๊ณ , getSlot()์€ @ManyToOne ๊ธฐ๋ณธ EAGER์ด๋ฏ€๋กœ JOIN์œผ๋กœ ํ•œ ๋ฐฉ์— ๋กœ๋“œ. ์ดํ›„ getTime()๋„ Slot ๋กœ๋“œ ์‹œ ํ•จ๊ป˜ ๋กœ๋”ฉ๋จ. (๋‹จ, fetch๋ฅผ LAZY๋กœ ๋ช…์‹œํ•˜๋ฉด getSlot() ์ ‘๊ทผ ์‹œ์ ์— ๋”ฐ๋กœ ์กฐํšŒ๋จ โ€” ํ˜„์žฌ ๋งคํ•‘ ๊ธฐ์ค€์œผ๋กœ ๋‹ตํ•จ.)


๋‚จ์€ ์ž‘์—… (๋‹ค์Œ PR)

  • N+1 ์žฌํ˜„ ํ›„ fetch join์œผ๋กœ ํ•ด๊ฒฐ (2๋‹จ๊ณ„)
  • update ๋กœ์ง ๋ณ€๊ฒฝ ๊ฐ์ง€ ๋ฐฉ์‹์œผ๋กœ ์™„์ „ ์ „ํ™˜
  • ์—”ํ‹ฐํ‹ฐ ์ง์ ‘ ๋ฐ˜ํ™˜ ๊ตฌ๊ฐ„ DTO ๋ณ€ํ™˜

๋ฏธ์™„์€ ์œ„์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์–ด๋””๊นŒ์ง€ ํ–ˆ๊ณ  ๋ฌด์—‡์ด ๋‚จ์•˜๋Š”์ง€ ๋ช…ํ™•ํžˆ ์ ์—ˆ์œผ๋‹ˆ, ์„ค๊ณ„ ํ† ๋ก  ์‹œ ์ด ์ง€์ ๋“ค์„ ํ•จ๊ป˜ ๋…ผ์˜ํ•˜๋ฉด ์ข‹๊ฒ ์Šต๋‹ˆ๋‹ค.

@soohyun1904

Copy link
Copy Markdown
Author

๐Ÿ’ฌ 2๋‹จ๊ณ„ โ€” ๋‚ด ์˜ˆ์•ฝ ๋ชฉ๋ก ์กฐํšŒ (์ฟผ๋ฆฌ ๋ฉ”์„œ๋“œ / N+1 / fetch join vs @EntityGraph)

๋“ค์–ด๊ฐ€๊ธฐ ์ „ ์ž๊ธฐ ์ง„๋‹จ

Q. ๋ฉ”์„œ๋“œ ์ด๋ฆ„ ์ฟผ๋ฆฌ vs JPQL ์ค‘ ๋ฌด์—‡์ด ๋จผ์ € ๋– ์˜ค๋ฅด๋‚˜? ๊ทผ๊ฑฐ๋Š”?

ํ˜„์žฌ ํ…Œ์ด๋ธ”์€ member ์—†์ด ์ด๋ฆ„๋งŒ ๋ณด์œ ํ•˜์ง€๋งŒ, name์ด Member ์—”ํ‹ฐํ‹ฐ๋กœ ์Šน๊ฒฉ๋˜๋ฉด ์กฐ์ธ์ด ํ•„์š”ํ•˜๋‹ค. ํ•œ ์‚ฌ๋žŒ์ด ์—ฌ๋Ÿฌ ์˜ˆ์•ฝ์„ ๊ฐ€์ง€๋ฏ€๋กœ ์˜ˆ์•ฝ N : ์‚ฌ๋žŒ 1 ๊ตฌ์กฐ๊ฐ€ ๋œ๋‹ค.


Q. ๋ฉ”์„œ๋“œ ์ด๋ฆ„ ์ฟผ๋ฆฌยทJPQL ์ค‘ ์–ด๋А ๊ฒƒ์„ ์ผ๋‚˜?

๋‹จ๊ณ„๋ณ„๋กœ ์˜ฎ๊ฒจ๊ฐ€๋ฉฐ ํ’€์—ˆ๋‹ค.

๋‹จ๊ณ„ ๋ฐฉ์‹ ๊ฒฐ๊ณผ
1 ๋ฉ”์„œ๋“œ ์ด๋ฆ„ ์ฟผ๋ฆฌ + ์ปจํŠธ๋กค๋Ÿฌ์—์„œ DTO ๋ณ€ํ™˜ LazyInitializationException
2 OSIV ์ผœ์„œ ํšŒํ”ผ ๋™์ž‘ํ•˜๋‚˜ N+1 ๋ฐœ๊ฒฌ
3 fetch join์œผ๋กœ ๊ฒ€์ฆ โ†’ @EntityGraph ์ตœ์ข… ์ฑ„ํƒ LazyInit + N+1 ๋™์‹œ ํ•ด๊ฒฐ, OSIV ๋‹ค์‹œ ๋”

1) LazyInitializationException

  • ์ปจํŠธ๋กค๋Ÿฌ(ํŠธ๋žœ์žญ์…˜ ๋ฐ–)์—์„œ DTO ๋ณ€ํ™˜ ์ค‘ member.getName() ๋“ฑ LAZY ์ ‘๊ทผ โ†’ ์„ธ์…˜ ๋‹ซํžŒ ์ƒํƒœ๋ผ ํ”„๋ก์‹œ ์ดˆ๊ธฐํ™” ์‹คํŒจ.
    2) N+1 ๋ฐœ๊ฒฌ
  • open-in-view=false๋ฅผ ์ฃผ์„ ์ฒ˜๋ฆฌ(OSIV ์ผฌ)ํ•ด LazyInit์€ ํšŒํ”ผํ–ˆ์œผ๋‚˜, ๋กœ๊ทธ์ƒ ์˜ˆ์•ฝ ๊ฑด๋งˆ๋‹ค member/slot/slot.theme/slot.time์„ ๊ฐœ๋ณ„ SELECT โ†’ N๋ฐฐ ํญ์ฆ.
    3) ํ•ด๊ฒฐ โ€” fetch join ๊ฒ€์ฆ ํ›„ @EntityGraph ์ฑ„ํƒ
  • fetch join(JPQL)์œผ๋กœ JOIN ํ•œ ๋ฐฉ์— ๊ฐ€์ ธ์˜ค๋ฉด LazyInitยทN+1์ด ๋™์‹œ ํ•ด๊ฒฐ๋จ์„ ํ™•์ธ.
  • ์กฐ๊ฑด์ด member.id ํ•˜๋‚˜๋กœ ๋‹จ์ˆœ โ†’ ์ตœ์ข…์€ ๋ฉ”์„œ๋“œ ์ด๋ฆ„ ์ฟผ๋ฆฌ + @EntityGraph๋กœ ์ •๋ฆฌ.
  • ํ•ด๊ฒฐ ํ›„ open-in-view=false ๋ณต๊ตฌ(๋‹ค์‹œ ๋”). OSIV๋Š” ๋ฌธ์ œ๋ฅผ ๊ฐ€๋ฆด ๋ฟ, ์—ฐ๊ด€ ์ฆ‰์‹œ ๋กœ๋”ฉ์ด ์ง„์งœ ํ•ด๊ฒฐ.
    ๊ฒ€์ฆ์— ์“ด fetch join:
@Query("select r from Reservation r " +
       "join fetch r.member join fetch r.slot s " +
       "join fetch s.theme join fetch s.time " +
       "where r.member.id = :memberId")
List<Reservation> findMineWithDetails(@Param("memberId") Long memberId);

ํ•ด๊ฒฐ ํ›„ ๋กœ๊ทธ โ€” JOIN ํ•œ ๋ฐฉ์œผ๋กœ ํ•ฉ์ณ์ง:

select r1_0.id, ..., m1_0.id, m1_0.name, ..., t1_0.*, t2_0.*
from reservation r1_0
join member m1_0 on m1_0.id = r1_0.member_id
join slot s1_0 on s1_0.id = r1_0.slot_id
join theme t1_0 on t1_0.id = s1_0.theme_id
join reservation_time t2_0 on t2_0.id = s1_0.time_id
where m1_0.id = ?

์ตœ์ข… ์ฑ„ํƒ โ€” @EntityGraph

@EntityGraph(attributePaths = {"member", "slot", "slot.theme", "slot.time"})
List<Reservation> findByMemberId(Long memberId);

๊ฒฝ๋กœ ํ‘œ๊ธฐ: member/slot์€ Reservation ์ง์† ํ•„๋“œ โ†’ ๊ทธ๋ƒฅ ์ด๋ฆ„. slot.theme/slot.time์€ Slot์— ์žˆ์–ด slot์„ ๊ฑฐ์ณ์•ผ ๋‹ฟ์Œ โ†’ ๊ฒฝ๋กœ๋กœ.

fetch join๊ณผ ์ฐจ์ด: ๋ฉ”์„œ๋“œ ์ด๋ฆ„ ์ฟผ๋ฆฌ์— ์–น์„ ์ˆ˜ ์žˆ๊ณ (fetch join์€ JPQL ํ•„์š”), ์ฟผ๋ฆฌยท์„ฑ๋Šฅ์€ ์‚ฌ์‹ค์ƒ ๋™์ผ.

์กฐ์ธ INNER/LEFT ๊ทœ์น™ (๊ทธ๋ž˜ํ”„ ๊ด€์ฐฐ):

  • optional=false(NOT NULL)๋ฉด Hibernate๊ฐ€ INNER๋กœ ์ตœ์ ํ™” โ†’ ๋กœ๊ทธ์— join(ํ˜„์žฌ ์ „๋ถ€ optional=false).
  • ๊ทœ์น™โ‘ : ๊ฐ™์€ ๊นŠ์ด ์—ฐ๊ด€์€ ๊ฐ์ž optional๋กœ ๊ฒฐ์ •(ํ˜•์ œ ๋…๋ฆฝ).
  • ๊ทœ์น™โ‘ก: ๋ถ€๋ชจ๊ฐ€ LEFT๋ฉด ์ž์‹๋„ LEFT ์ „ํŒŒ(slot์ด LEFT๋ฉด slot.theme๋„ LEFT).
  • @EntityGraph๋Š” optional ๋ณด๊ณ  ์ž๋™, join fetch๋Š” ๋ฌด์กฐ๊ฑด INNER(LEFT๋Š” left join fetch).
    ์„ ํƒ ์ด์œ : ์กฐ๊ฑด์ด ๋‹จ์ˆœ(member.id ํ•˜๋‚˜)์ด๋ผ ๋ฉ”์„œ๋“œ ์ด๋ฆ„ ์ฟผ๋ฆฌ๋กœ ์ถฉ๋ถ„. ์—ฐ๊ด€ ๋กœ๋”ฉ๋งŒ ์–ด๋…ธํ…Œ์ด์…˜ ํ•œ ์ค„๋กœ ๋ถ„๋ฆฌ โ†’ ์ฟผ๋ฆฌ ๊น”๋”. optional=false๋ผ ๊ทธ๋ž˜ํ”„์—ฌ๋„ INNER๋กœ ๋‚˜๊ฐ€ fetch join๊ณผ ๊ฒฐ๊ณผยท์„ฑ๋Šฅ ๋™์ผ.

Q. ๊ทธ ๊ฒฐ์ •์˜ ํ•œ๊ณ„๋Š”?

ํ•œ๊ณ„ ๋‚ด์šฉ fetch join @EntityGraph
ํŽ˜์ด์ง• ์ปฌ๋ ‰์…˜+ํŽ˜์ด์ง• โ†’ ๋ฉ”๋ชจ๋ฆฌ ํŽ˜์ด์ง•(์œ„ํ—˜). ๋‹จ์ผ ๊ฐ์ฒด OK ํ•ด๋‹น ํ•ด๋‹น
์ปฌ๋ ‰์…˜ ๋‹ค์ค‘ @onetomany ๋‘˜ ์ด์ƒ โ†’ MultipleBagFetchException ํ•ด๋‹น ํ•ด๋‹น
์„ฌ์„ธํ•œ ์ฟผ๋ฆฌ ์ผ๋ถ€ ์ปฌ๋Ÿผ/์—ฐ๊ด€๋ณ„ ์กฐ๊ฑด/์ง‘๊ณ„ ๋ถˆ๊ฐ€ โ†’ DTOยทQueryDSL ํ•ด๋‹น ๋” ์•ฝํ•จ(WHERE ๋ชป ๊ฒ€)
๊ณผ์กฐํšŒ ์•ˆ ์“ฐ๋Š” ์—ฐ๊ด€๊นŒ์ง€ ํ•ด๋‹น ํ•ด๋‹น
INNER/LEFT ์ œ์–ด ์กฐ์ธ ๊ฐ•์ œ left join fetch ๊ฐ€๋Šฅ ์ž๋™, ๊ฐ•์ œ ์–ด๋ ค์›€

ํ˜„์žฌ ๊ธฐ์ค€: member/slot/theme/time ๋ชจ๋‘ @ManyToOne(๋‹จ์ผ)์ด๋ผ ํ–‰์ด ์•ˆ ๋Š˜์–ด ํŽ˜์ด์ง•ยท์ปฌ๋ ‰์…˜ ๋‹ค์ค‘์€ ํ•ด๋‹น ์—†์Œ.

ํ•ต์‹ฌ: ๋‹จ์ˆœ ์กฐํšŒ๋Š” @EntityGraph, ๋ณต์žกํ•œ ์กฐ๊ฑด์€ fetch join. ์ •๊ตํ•œ ์ฟผ๋ฆฌ(์ผ๋ถ€ ์ปฌ๋Ÿผยท์ง‘๊ณ„ยท์—ฐ๊ด€๋ณ„ ์กฐ๊ฑด)๋Š” DTO ์กฐํšŒ/QueryDSL๋กœ.

@soohyun1904

Copy link
Copy Markdown
Author

๐Ÿ’ฌ 3๋‹จ๊ณ„ โ€” ์˜ˆ์•ฝ ๋Œ€๊ธฐ ๊ธฐ๋Šฅ (๋„๋ฉ”์ธ ์„ค๊ณ„ ํŒ๋‹จ / JPQL ๋ณธ๊ฒฉ)

๋“ค์–ด๊ฐ€๊ธฐ ์ „ ์ž๊ธฐ ์ง„๋‹จ

Q. ์˜ˆ์•ฝ ๋Œ€๊ธฐ๋ฅผ ๋ณ„๋„ ์—”ํ‹ฐํ‹ฐ๋กœ ๋งŒ๋“ค์ง€, Reservation์— status ์ปฌ๋Ÿผ๋งŒ ์ถ”๊ฐ€ํ• ์ง€? ์ฒซ ์ง๊ฐ๊ณผ ๊ทผ๊ฑฐ๋Š”?

์ฒซ ์ง๊ฐ โ€” status ์ปฌ๋Ÿผ (์„ ํƒ A)

@Entity
public class Reservation {
    @Enumerated(EnumType.STRING)
    private Status status;   // APPROVED, WAITING
}

๊ทผ๊ฑฐ: ๋Œ€๊ธฐ์™€ ํ™•์ •์€ ๋ณธ์งˆ์ ์œผ๋กœ ๊ฐ™์€ "์˜ˆ์•ฝ ํ–‰์œ„"์ด๊ณ  ์Šน์ธ ์—ฌ๋ถ€๋งŒ ๋‹ค๋ฅด๋‹ค. ๋Œ€๊ธฐ ์Šน์ธ์€ WAITING โ†’ APPROVED๋กœ ์ƒํƒœ๋งŒ ๋ฐ”๊พธ๋ฉด ๋˜๋‹ˆ, ๋ณ„๋„ ์—”ํ‹ฐํ‹ฐ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ์˜ฎ๊ธฐ๋Š” ๊ฒƒ๋ณด๋‹ค ์ž์—ฐ์Šค๋Ÿฝ๋‹ค.

๋ถ„๋ฆฌ(์„ ํƒ B)๋„ ๊ณ ๋ ค โ€” ํŠธ๋ ˆ์ด๋“œ์˜คํ”„

status ์ปฌ๋Ÿผ ๋ณ„๋„ ์—”ํ‹ฐํ‹ฐ
status null ์œ„ํ—˜ ์žˆ์Œ ์—†์Œ(ํƒ€์ž…์ด ์ƒํƒœ)
ํƒ€์ž… ์•ˆ์ „์„ฑ ๋Ÿฐํƒ€์ž„ ๋ถ„๊ธฐ ์ปดํŒŒ์ผ ํƒ€์ž„
๋Œ€๊ธฐโ†”ํ™•์ • ์ „ํ™˜ ๊ฐ’๋งŒ ๋ณ€๊ฒฝ ํ…Œ์ด๋ธ” ์ด์‚ฌ
ํ†ตํ•ฉ ์กฐํšŒ ํ•œ ํ…Œ์ด๋ธ” ๋‘ ํ…Œ์ด๋ธ” ํ•ฉ์นจ

ํŒ๋‹จ ๊ธฐ์ค€ = ์ „ํ™˜ ๋นˆ๋„: ๋ฐฉํƒˆ์ถœ์€ ๋Œ€๊ธฐโ†’ํ™•์ • ์ „ํ™˜์ด ์žฆ์Œ(์ทจ์†Œ ์‹œ ์ž๋™ ์Šน๊ฒฉ). ๋ถ„๋ฆฌํ•˜๋ฉด ์ „ํ™˜๋งˆ๋‹ค ๋ฐ์ดํ„ฐ ์ด์‚ฌ โ†’ ๋ถ€๋‹ด. status๋Š” changeStatus() ๊ฐ’ ํ•˜๋‚˜๋กœ ๋. โ†’ ๋ถ„๋ฆฌ์˜ null ์•ˆ์ „์„ฑ๋ณด๋‹ค ์žฆ์€ ์ „ํ™˜ ๋น„์šฉ์ด ์ปค status ์„ ํƒ.

๊ฒช์€ ํ•œ๊ณ„ โ€” status null ๋ฌธ์ œ: ์ƒ์„ฑ ์‹œ status ์•ˆ ์ฑ„์šฐ๋ฉด null ๊ฐ€๋Šฅ. ์˜์†ํ™” ์ „์—” ๋ณด์žฅ ์•ฝํ•จ.
โ†’ ํ•ด๊ฒฐ์€ ๋ถ„๋ฆฌ๊ฐ€ ์•„๋‹ˆ๋ผ ์ƒ์„ฑ ๊ฐ•์ œ: create()์—์„œ status ํ•ญ์ƒ ์„ธํŒ… + DB not null ์ œ์•ฝ. "์œ ํšจํ•˜์ง€ ์•Š์€ ๊ฐ์ฒด๋ฅผ ๋ชป ๋งŒ๋“ค๊ฒŒ" ๋ง‰์œผ๋ฉด null ์‚ฌ๋ผ์ง€๊ณ  status ์žฅ์  ์œ ์ง€.

์ƒํƒœ ํŒจํ„ด์€? ์ƒํƒœ 2๊ฐœ(๋Œ€๊ธฐ/ํ™•์ •)๋ฟ์ด๋ผ enum์œผ๋กœ ์ถฉ๋ถ„. ์ƒํƒœ ํŒจํ„ด์€ ์ƒํƒœ๊ฐ€ ์—ฌ๋Ÿฟ์ด๊ณ  ์ „์ด๊ฐ€ ๋ณต์žกํ•  ๋•Œ. ํ˜„์žฌ๋Š” ๊ณผํ•จ.


3-1. N+1๊ณผ fetch join ๋น„๊ต (๊ด€์ฐฐ ๊ณผ์ œ 2)

GET /reservations-mine์—์„œ ์˜ˆ์•ฝ N + ๋Œ€๊ธฐ M์„ ๊ฐ€์ ธ์™€ DTO ๋ณ€ํ™˜ ์‹œ:

  • ๋ณ€ํ™˜ ์ „(N+1): ํ•ญ๋ชฉ๋งˆ๋‹ค getTheme().getName()ยทgetTime().getStartAt() ์ ‘๊ทผ โ†’ ๊ฐœ๋ณ„ SELECT๊ฐ€ ํ•ญ๋ชฉ ์ˆ˜๋งŒํผ ํญ์ฆ.
  • fetch join/@EntityGraph ์ ์šฉ ํ›„: JOIN ํ•œ ๋ฐฉ์œผ๋กœ ํ•ฉ์ณ์ ธ ์ฟผ๋ฆฌ 1๊ฐœ.
  • row ์ค‘๋ณต: ๋‹จ์ผ ์—ฐ๊ด€(@manytoone)๋งŒ fetch๋ผ ํ–‰ ์•ˆ ๋Š˜์–ด๋‚จ(์ค‘๋ณต ์—†์Œ).

2๋‹จ๊ณ„ ํšŒ๊ณ ์— before/after ๋กœ๊ทธ๋ฅผ ๋‚˜๋ž€ํžˆ ๊ธฐ๋กํ•จ.


3-2. JPQL ๋ณธ๊ฒฉ โ€” N๋ฒˆ์งธ ๋Œ€๊ธฐ ๊ณ„์‚ฐ

๋ฉ”์„œ๋“œ ์ด๋ฆ„ ์ฟผ๋ฆฌ๋กœ ๋ชป ํ’‚ โ†’ JPQL + ์„œ๋ธŒ์ฟผ๋ฆฌ + CASE WHEN.

@Query("""
    SELECT r.id,
        CASE WHEN r.status = ...APPROVED THEN 0L
             ELSE (SELECT COUNT(w) + 1L FROM Reservation w
                   WHERE w.slot = r.slot
                     AND w.status = ...WAITING
                     AND w.createdAt < r.createdAt)
        END
    FROM Reservation r WHERE r.member.id = :memberId
    """)

Q. JPQL์ด ๋ฐœํ–‰ํ•˜๋Š” SQL

select r1_0.id,
    case when r1_0.status='APPROVED' then 0
         else (select (count(r2_0.id)+1) from reservation r2_0
               where r2_0.slot_id=r1_0.slot_id
                 and r2_0.status='WAITING'
                 and r2_0.created_at<r1_0.created_at)
    end
from reservation r1_0
where r1_0.member_id=?

Q. ๋ฉ”์„œ๋“œ ์ด๋ฆ„ ์ฟผ๋ฆฌ๋กœ๋Š” ์™œ ๋ชป ํ‘ธ๋‚˜? (1์ค„)

๋ฉ”์„œ๋“œ ์ด๋ฆ„ ์ฟผ๋ฆฌ๋Š” ๋‹จ์ˆœ ์กฐ๊ฑด ์กฐํ•ฉ๋งŒ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ์–ด, CASE WHENยท์„œ๋ธŒ์ฟผ๋ฆฌยท์ง‘๊ณ„ ๊ฐ™์€ ์—ฐ์‚ฐ์€ ์• ์ดˆ์— ํ‘œํ˜„์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์ฐธ๊ณ : ๊ฒฐ๊ณผ๋ฅผ Object[]๋กœ ๋ฐ›์•„ ๊ฐ€๊ณต ์ค‘ โ†’ DTO ์ƒ์„ฑ์ž ํ‘œํ˜„์‹(SELECT new ...Rank(...))์œผ๋กœ ๋ฐ”๊พธ๋ฉด ์บ์ŠคํŒ… ์—†์ด ํƒ€์ž… ์•ˆ์ „. (๊ฐœ์„  ์—ฌ์ง€)

@soohyun1904

Copy link
Copy Markdown
Author

๐Ÿ’ฌ 4๋‹จ๊ณ„ โ€” ์˜ˆ์•ฝ ๋Œ€๊ธฐ ๊ด€๋ฆฌ + ์ž๋™ ์Šน์ธ (ํŠธ๋žœ์žญ์…˜ ๊ฒฝ๊ณ„ / ๋™์‹œ์„ฑ / flush ์ˆœ์„œ)

๋“ค์–ด๊ฐ€๊ธฐ ์ „ ์ž๊ธฐ ์ง„๋‹จ

Q. ์ž๋™ ์Šน์ธ ๋กœ์ง์„ ์–ด๋””์— ๋‘˜ ๊ฒƒ์ธ๊ฐ€? ์ฒซ ์ง๊ฐ๊ณผ ๊ทผ๊ฑฐ๋Š”?

์ž๋™ ์Šน์ธ์€ ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ๋ฌธ์ œ๋‹ค. ์ทจ์†Œ/์ˆ˜์ • ํ›„ ์ž๋™ ์Šน์ธ์ด ์ œ๋Œ€๋กœ ๋ฐ˜์˜ ์•ˆ ๋˜๋ฉด "์Šน์ธ์ด ๋น„์–ด๋ฒ„๋ฆฐ ์˜ˆ์•ฝ"์ด ์ƒ๊ธธ ์ˆ˜ ์žˆ๋‹ค. ๊ทธ๋Ÿฌ๋‹ˆ ํ†ต์งธ๋กœ ํ•˜๋‚˜์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง(=ํ•˜๋‚˜์˜ ํŠธ๋žœ์žญ์…˜)์œผ๋กœ ๋ฌถ์–ด์•ผ ํ•œ๋‹ค.

Q. ํŠธ๋žœ์žญ์…˜ ๊ฒฝ๊ณ„๋Š” ์–ด๋””๊นŒ์ง€ ๊ตณํ˜€์•ผ ํ•˜๋‚˜?

ํŠธ๋žœ์žญ์…˜์€ ์›์ž์„ฑ์ด ํ•„์š”ํ•œ ๋ฒ”์œ„๊นŒ์ง€ ๋ฌถ๋Š”๋‹ค โ€” ํ•˜๋‚˜๋ผ๋„ ์‹คํŒจํ•˜๋ฉด ์ „๋ถ€ ๋กค๋ฐฑ๋ผ์•ผ ํ•˜๋Š” ์ž‘์—…๋“ค๊นŒ์ง€. ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ์ด ๊ฑธ๋ฆฐ ๊ฑด(์ƒํƒœ ์ „์ด) ๋ฌถ๊ณ , ๋˜๋Œ๋ฆด ํ•„์š” ์—†๋Š” ๊ฑด(์•Œ๋ฆผยท๋กœ๊ทธยท์™ธ๋ถ€ ํ˜ธ์ถœ) ํŠธ๋žœ์žญ์…˜ ๋ฐ–์œผ๋กœ ๋บ€๋‹ค. ํŠธ๋žœ์žญ์…˜์€ ์งง๊ฒŒ ์œ ์ง€.


Q. ์ž๋™ ์Šน์ธ ๋กœ์ง์˜ ์œ„์น˜๋Š”?

์ˆ˜์ •ยท์‚ญ์ œ์—์„œ ๋ณ€๊ฒฝ(์ทจ์†Œ ๋“ฑ)์ด ์ผ์–ด๋‚˜๋ฉด, ๊ฐ™์€ ํ๋ฆ„์—์„œ ์ž๋™ ์Šน์ธ์ด ์ผ์–ด๋‚œ๋‹ค. (๋ณ„๋„ ์ด๋ฒคํŠธ๋กœ ๋ถ„๋ฆฌํ•˜์ง€ ์•Š๊ณ  Service ๋ฉ”์„œ๋“œ ๋‚ด ์ฒ˜๋ฆฌ)

๋ณด์ถฉ: ์ด๋ฒคํŠธ๋กœ ๋บ„ ์ˆ˜๋„ ์žˆ์œผ๋‚˜(๋™๊ธฐ ์ด๋ฒคํŠธ๋ฉด ๊ฐ™์€ ํŠธ๋žœ์žญ์…˜ ์œ ์ง€ ๊ฐ€๋Šฅ), ํ•  ์ผ์ด ์ž๋™ ์Šน์ธ ํ•˜๋‚˜๋ฟ์ด๊ณ  ์ทจ์†Œ์™€ ๋ฐ€์ ‘ํ•˜๋ฏ€๋กœ ๋ฉ”์„œ๋“œ ๋ถ„๋ฆฌ๊ฐ€ ๊ฐ€์žฅ ๋‹จ์ˆœ. ์ด๋ฒคํŠธ๋Š” ํ•  ์ผ์ด ์—ฌ๋Ÿฌ ๊ฐœ๋กœ ๋Š˜๊ณ  ๋ชจ๋“ˆ์ด ๊ฐˆ๋ฆด ๋•Œ ์œ ํšจ.


๋ณธ์งˆ ์‹ ํ˜ธ ์ ๊ฒ€

โ‘  ํŠธ๋žœ์žญ์…˜ ๊ฒฝ๊ณ„ โ€” ํ•œ ๋ฉ”์„œ๋“œ vs ๋ถ„๋ฆฌ

  • ํ•œ ๋ฉ”์„œ๋“œ(@transactional) ์•ˆ์—์„œ ๋‘˜ ๋‹ค ์ฒ˜๋ฆฌ.
  • ์ทจ์†Œ์™€ ์ž๋™ ์Šน์ธ์ด ๋™์‹œ์— ์„ฑ๊ณต/์‹คํŒจํ•ด์•ผ ํ•œ๋‹ค(์›์ž์„ฑ). ํ•˜๋‚˜๋งŒ ๋ฐ˜์˜๋˜๋ฉด "์ทจ์†Œ๋Š” ๋๋Š”๋ฐ ๋‹ค์Œ ๋Œ€๊ธฐ์ž ์Šน์ธ ์•ˆ ๋œ" ๋ถˆ์ผ์น˜.
    โ‘ก ๋™์‹œ์„ฑ โ€” ๊ฐ™์€ ์ž๋ฆฌ ๋™์‹œ ์ทจ์†Œยท์Šน์ธ
  • ํŠธ๋žœ์žญ์…˜์€ ๊ทธ ์‚ฌ์ด ๋‹ค๋ฅธ ํŠธ๋žœ์žญ์…˜์ด ๊ฐ™์€ ๊ฐ’์„ ๋ฐ”๊พธ๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•˜์ง€ ์•Š์Œ โ†’ race condition ๊ฐ€๋Šฅ.
  • ๋‘ ์‚ฌ์šฉ์ž๊ฐ€ ๋™์‹œ์— ๊ฐ™์€ ์Šฌ๋กฏ ์ฒ˜๋ฆฌ โ†’ ๋‘˜ ๋‹ค "๋นˆ์ž๋ฆฌ"๋กœ ์ฝ๊ณ  ๋‘˜ ๋‹ค ์Šน์ธ โ†’ ํ•œ ์ž๋ฆฌ 2๋ช….
  • H2๋ผ์„œ ๋ถ€๋ถ„ ์œ ๋‹ˆํฌ ์ œ์•ฝ("status=APPROVED์ผ ๋•Œ๋งŒ slot_id ์œ ๋‹ˆํฌ")์„ ๊ฑธ๊ธฐ ์–ด๋ ค์›Œ ๋” ์ทจ์•ฝ.
  • ํ•ด๊ฒฐ ๋ฐฉํ–ฅ: ์ž๋™ ์Šน์ธ ๋กœ์ง ์•ž์—์„œ ์Šฌ๋กฏ์— ๋น„๊ด€์  ๋ฝ(@Lock(PESSIMISTIC_WRITE))์„ ๊ฑธ์–ด, ๊ฐ™์€ ์Šฌ๋กฏ์„ ์ฒ˜๋ฆฌํ•˜๋ ค๋Š” ํŠธ๋žœ์žญ์…˜๋“ค์„ ์Šฌ๋กฏ ๋ฝ ์•ž์—์„œ ์ˆœ์ฐจํ™”. (FK๊ฐ€ ๋ฝ์„ ์ „ํŒŒํ•˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ์Šฌ๋กฏ ํ–‰์„ ๋จผ์ € ์ž ๊ฐ€ ์ˆœ์„œ๋ฅผ ๋งŒ๋“œ๋Š” ๊ฒƒ.)
    โ‘ข flush ์ˆœ์„œ โ€” Reservation/Waiting ๋‘ ๋„๋ฉ”์ธ ํ•จ๊ป˜ ๋ณ€๊ฒฝ ์‹œ
  • flush๋Š” ํ˜ธ์ถœ ์ˆœ์ด ์•„๋‹ˆ๋ผ ํƒ€์ž…๋ณ„ ์ˆœ์„œ: INSERT โ†’ UPDATE โ†’ ์ปฌ๋ ‰์…˜ ์‚ญ์ œ โ†’ ์ปฌ๋ ‰์…˜ ์ถ”๊ฐ€/์ˆ˜์ • โ†’ DELETE.
  • "์‚ญ์ œ ํ›„ ์ƒ์„ฑ/์ˆ˜์ •" ์ˆœ์„œ๊ฐ€ ํ•„์š”ํ•˜๋ฉด ์ค‘๊ฐ„์— em.flush()๋กœ DELETE๋ฅผ ๋จผ์ € ๊ฐ•์ œ.
  • flush์™€ ํŠธ๋žœ์žญ์…˜: ์ƒ๊ด€์—†๋‹ค. flush๋Š” SQL์„ DB์— ์ „์†กํ•  ๋ฟ, ์ปค๋ฐ‹ ์ „๊นŒ์ง„ ํ™•์ • ์•„๋‹˜. ์ค‘๊ฐ„ ์‹คํŒจ ์‹œ flushํ•œ ๊ฒƒ๊นŒ์ง€ ์ „๋ถ€ ๋กค๋ฐฑ โ†’ ์ •ํ•ฉ์„ฑ ์˜ํ–ฅ ์—†์Œ.

๋‹จ, ํ˜„์žฌ๋Š” status ๋‹จ์ผ ์—”ํ‹ฐํ‹ฐ๋ผ ์ž๋™ ์Šน์ธ์ด UPDATE ํ•˜๋‚˜๋ฟ โ†’ INSERT/DELETE๊ฐ€ ์—†์–ด flush ์ˆœ์„œ ๋ฌธ์ œ๋ฅผ ์‹ค์ œ๋กœ ๋งŒ๋‚˜์ง€ ์•Š์Œ. (๋ณ„๋„ Waiting ์—”ํ‹ฐํ‹ฐ์˜€๋‹ค๋ฉด ๋งŒ๋‚ฌ์„ ์‹ ํ˜ธ.)


Q. ๊ทธ ๊ฒฐ์ •์˜ ํ•œ๊ณ„ โ€” ํŠธ๋žœ์žญ์…˜ ๊ฒฝ๊ณ„ยท๋™์‹œ์„ฑยท์ผ๊ด€์„ฑ ์ค‘ ๊ฐ€์žฅ ์•ฝํ•œ ๊ฒƒ์€?

๋™์‹œ์„ฑ์ด ๊ฐ€์žฅ ์•ฝํ•˜๋‹ค.

ํ•ญ๋ชฉ ์ƒํƒœ
ํŠธ๋žœ์žญ์…˜ ๊ฒฝ๊ณ„ ํ•œ ๋ฉ”์„œ๋“œ ํ•œ ํŠธ๋žœ์žญ์…˜์œผ๋กœ ์›์ž์„ฑ ํ™•๋ณด โ†’ ๊ฒฌ๊ณ 
์ผ๊ด€์„ฑ(๋‹จ์ผ ํŠธ๋žœ์žญ์…˜ ๋‚ด) flush/๋กค๋ฐฑ์œผ๋กœ ๋ณด์žฅ โ†’ ๊ฒฌ๊ณ 
๋™์‹œ์„ฑ(ํŠธ๋žœ์žญ์…˜ ๊ฐ„) ๋ฐฉ์–ด ์žฅ์น˜ ์—†์Œ โ†’ ๊ฐ€์žฅ ์•ฝํ•จ
  • ๋‘ ์‚ฌ์šฉ์ž๊ฐ€ ๋™์‹œ์— ๊ฐ™์€ ์Šฌ๋กฏ ์ฒ˜๋ฆฌ ์‹œ ๋‘˜ ๋‹ค ์Šน์ธ๋˜๋Š” race condition.
  • H2 ๋ถ€๋ถ„ ์œ ๋‹ˆํฌ ์ œ์•ฝ ํ•œ๊ณ„๋กœ DB ๋ ˆ๋ฒจ ๋ฐฉ์–ด๋„ ์–ด๋ ค์›€.
  • โ†’ ๋น„๊ด€์  ๋ฝ(์Šฌ๋กฏ ํ–‰ ๋ฝ)์œผ๋กœ ์ˆœ์ฐจํ™”ํ•˜๋Š” ๊ฒƒ์ด ๋ณด์™„์ฑ….
    ํ•œ ์ค„: ๋‹จ์ผ ํŠธ๋žœ์žญ์…˜์˜ ์›์ž์„ฑยท์ผ๊ด€์„ฑ์€ ํ™•๋ณดํ–ˆ์œผ๋‚˜, ํŠธ๋žœ์žญ์…˜ ๊ฐ„ ๋™์‹œ์„ฑ์ด ๊ฐ€์žฅ ์•ฝํ•˜๋‹ค. ๊ฐ™์€ ์Šฌ๋กฏ ๋™์‹œ ์ฒ˜๋ฆฌ ์‹œ race condition์ด ๋‚จ์•„ ์žˆ๊ณ , ๋น„๊ด€์  ๋ฝ์œผ๋กœ ๋ณด์™„ํ•ด์•ผ ํ•œ๋‹ค.

๋™์‹œ์„ฑ ๋ณด์™„ โ€” ๋ฝ ์„ ํƒ (์ฐธ๊ณ )

๋ฐฉ์‹ ๋™์ž‘ ์–ธ์ œ
DB ์œ ๋‹ˆํฌ ์ œ์•ฝ DB๊ฐ€ ์ค‘๋ณต ์ฐจ๋‹จ 1์ˆœ์œ„(๋‹จ H2 ๋ถ€๋ถ„ ์œ ๋‹ˆํฌ ํ•œ๊ณ„)
๋‚™๊ด€์  ๋ฝ(@Version) ์ €์žฅ ์‹œ ๋ฒ„์ „ ๋น„๊ต, ์ถฉ๋Œ ์‹œ ์žฌ์‹œ๋„ ์ถฉ๋Œ ๋“œ๋ฌผ ๋•Œ
๋น„๊ด€์  ๋ฝ(@lock) ๋จผ์ € ํ–‰ ์ž ๊ธˆ, ์ˆœ์ฐจ ์ฒ˜๋ฆฌ ์ถฉ๋Œ ์žฆ๊ณ  ์ˆœ์ฐจ ํ•„์ˆ˜

์ž๋™ ์Šน์ธ์€ "ํ•œ ์ž๋ฆฌ 2๋ช…"์ด ์น˜๋ช…์ ์ด๋ผ ํ™•์‹คํ•œ ์ˆœ์ฐจ ์ฒ˜๋ฆฌ ํ•„์š” โ†’ ์Šฌ๋กฏ ๋น„๊ด€์  ๋ฝ์ด ์ ํ•ฉ.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant